<template>
  <div>
    <div class="uploadWrapper">
      <el-upload
          action="#"
          multiple
          :auto-upload="false"
          :show-file-list="true"
          :on-change="handleChange"
          drag
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">
          将文件拖到此处，或<em>点击上传</em>
        </div>
      </el-upload>
    </div>

    <!-- 进度显示 -->
    <div class="progress-box">
      <span>上传进度：{{ percent.toFixed() }}%</span>
      <el-button type="primary" size="small" @click="handleClickBtn">{{ upload ? '暂停' : '继续'}}</el-button>
    </div>

  </div>
</template>

<script>
import SparkMD5 from "spark-md5"
import { UploadFilled } from '@element-plus/icons-vue'
import api from '@/api'

export default {
  data() {
    return {
      percent: 0,
      fileUrl: '',
      upload: true,
      percentCount: 0,
      file: "",
    }
  },
  methods: {
    async handleChange(file) {
      if (!file) return
      this.percent = 0
      this.percentCount = 0
      this.videoUrl = ''
      // 获取文件并转成 ArrayBuffer 对象
      const fileObj = file.raw
      this.file = fileObj.name
      let buffer
      try {
        buffer = await this.fileToBuffer(fileObj)
      } catch (e) {
        console.log(e)
      }

      // 将文件按固定大小（6M）进行切片，注意此处同时声明了多个常量
      const chunkSize = 6291456,
          chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize), // 计算总共多个切片
          suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1] // 文件后缀名

      // 根据文件内容生成 hash 值
      const spark = new SparkMD5.ArrayBuffer()
      spark.append(buffer)
      const hash = spark.end()

      // 生成切片，这里后端要求传递的参数为字节数据块（chunk）和每个数据块的文件名（fileName）
      let curChunk = 0 // 切片时的初始位置
      for (let i = 0; i < chunkListLength; i++) {
        const item = {
          chunk: fileObj.slice(curChunk, curChunk + chunkSize),
          fileName: `${hash}_${i}.${suffix}` // 文件名规则按照 hash_1.jpg 命名
        }
        curChunk += chunkSize
        chunkList.push(item)
      }
      this.chunkList = chunkList // sendRequest 要用到
      this.hash = hash // sendRequest 要用到
      this.sendRequest()
    },

    // 发送请求
    sendRequest() {
      const requestList = [] // 请求集合
      let that = this
      this.chunkList.forEach((item, index) => {
        const fn = () => {
          const formData = new FormData()
          formData.append('chunk', item.chunk)
          formData.append('filename', item.fileName)
          return api.upload.chunk(formData).then(res => {
            if (res.code === 200) { // 成功
              if (that.percentCount === 0) { // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                that.percentCount = 100 / that.chunkList.length
              }
              if (that.percent >= 100) {
                that.percent = 100;
              }else {
                that.percent += that.percentCount // 改变进度
              }
              if (that.percent >= 100) {
                that.percent = 100;
              }
              that.chunkList.splice(index, 1) // 一旦上传成功就删除这一个 chunk，方便断点续传
            }
          })
        }
        requestList.push(fn)
      })

      let i = 0 // 记录发送的请求个数
      // 文件切片全部发送完毕后，需要请求 '/merge' 接口，把文件的 hash 传递给服务器
      const complete = () => {
        api.upload.merge(this.hash, this.file).then(res => {
          if (res.code === 200) { // 请求发送成功
            // this.videoUrl = res.data.path
            console.log(res)
          }
        })
      }
      const send = async () => {
        if (!this.upload) return
        if (i >= requestList.length) {
          // 发送完毕
          complete()
          return
        }
        await requestList[i]()
        i++
        send()
      }
      send() // 发送请求
    },

    // 按下暂停按钮
    handleClickBtn() {
      this.upload = !this.upload
      // 如果不暂停则继续上传
      if (this.upload) this.sendRequest()
    },

    // 将 File 对象转为 ArrayBuffer
    fileToBuffer(file) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader()
        fr.onload = e => {
          resolve(e.target.result)
        }
        fr.readAsArrayBuffer(file)
        fr.onerror = () => {
          reject(new Error('转换文件格式发生错误'))
        }
      })
    }
  },
  components: {
    UploadFilled
  }
}
</script>

<style scoped>
.progress-box {
  box-sizing: border-box;
  width: 360px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  padding: 8px 10px;
  background-color: #ecf1ff;
  font-size: 14px;
  border-radius: 4px;
}
.uploadWrapper {
  width: 360px;
}
</style>
